/*
 *  GameWorld.cpp
 *  sfmltest1
 *
 *  Created by Steven Hamilton on 21/06/09.
 *  Copyright 2009 __MyCompanyName__. All rights reserved.
 *
 */

#include <vector>
#include <iostream>
#include <fstream>
#include <stdlib.h>
#include <cmath>
#include "Creature.h"
#include "DisplayManager.h"
#include "fov.h"
#include "Map.h"
#include "NameGenerator.h"
#include "Terrain.h"
#include "GameWorld.h"
#include "Utility.h"
#include "defines.h"

GameWorld::GameWorld()
{
	objectIdIndex=0;
    
    consoleBuffer.resize(CONSOLEPANELHEIGHT);

	level=1;
	finalLevel=3;
    items.resize(0);
	creatures.resize(0);

	
	selectionGfxId=TARGET_S;
}

GameWorld::~GameWorld()
{
}

void GameWorld::initWorld()
{
    objectId=getObjectId();
    level=0;
    items.resize(0);
    creatures.resize(0);
    //generateMap();
	//regeneratePlayers();
	generateItems();
}

event_t	GameWorld::checkMatchStatus(event_t event)
{
	std::vector<Creature*> creatures;
	creatures=getCreatures(FRIEND,true);
	if (creatures.size()==0) {
		event=EVENT_MATCH_LOST;
	}
	creatures=getCreatures(ENEMY,true);
	if (creatures.size()==0) {
		event=EVENT_MATCH_WIN;
	}
	return event;
}

int GameWorld::getObjectId()
{
	//returns the next objectId;
	//std::cout << "objectIdIndex is " << objectIdIndex << std::endl;
    return objectIdIndex++;
}

void GameWorld::generateMap()
{	
	map=Map();
	map.loadMap(level);	
}

void GameWorld::generateItems()
{
    for (int i=-0;i<5;i++){
        Item * item=generateItem(PISTOL, objectId);
    }
	for (int i=-0;i<3;i++){
        Item * item=generateItem(RIFLE, objectId);
    }
//   std::cout << "WORLD items.size = " << items.size() << std::endl;
//   std::cout << "WORLD objectId = " << objectId << std::endl;
}

void GameWorld::regeneratePlayers()
{
    //this checks the global number of humans alive and tops them up
    Utility tool;
    srand (time(NULL));
	std::vector<Creature*> humans = getCreatures(FRIEND);
	std::cout << "Humans = " << humans.size() << std::endl;
	NameGenerator nameGen;
    if (humans.size() < NUMBEROFBASEPLAYERS) {
		int need = NUMBEROFBASEPLAYERS-humans.size();
		std::cout << "Topping up with " << need << " humans" << std::endl;
		for (int i = 0;i < need; i++){
			faction_t faction = FRIEND;
			Creature creature(HUMAN, faction);
			creature.skills["Pistol"]=10;
			creature.skills["Rifle"]=5;
			creature.name=nameGen.name();
			addCreature(creature);
        }
	}
    std::cout << "Now creatures = " << creatures.size() << std::endl;
}

void GameWorld::addCreature(Creature & creature)
{
    creatures.push_back(creature);
}

void GameWorld::delCreature(Creature  * creature)
{
    std::vector<Creature>::iterator it;

    for (it=creatures.begin();it<creatures.end();++it){
        std::cout << "Matching creature " << creature->name << " to " << it->name <<  std::endl;
        Creature * creat = &*it;
        if (creat==creature){
            std::cout << " and erasing" << std::endl;
            creatures.erase(it);
			break;
        }
    }
}

std::vector<Creature*> GameWorld::getCreatures(faction_t faction)
{
    std::vector<Creature*> results;
    //returns the creature for specified faction
    for (int i=0;i < creatures.size();i++){
        //iterate and check faction
        if (creatures[i].faction==faction && creatures[i].isAlive) results.push_back(&WORLD.creatures[i]);
    }
    return results;
}


std::vector<Creature*> GameWorld::getCreatures(faction_t faction, bool mapStatus)
{
    std::vector<Creature*> results;
    //returns the creature for specified faction
    for (int i=0;i < creatures.size();i++){
        //iterate and check faction
        if (creatures[i].faction==faction && creatures[i].onMap==mapStatus && creatures[i].isAlive) results.push_back(&WORLD.creatures[i]);
    }
    return results;
}

std::vector<Creature*> GameWorld::getCreatures(bool mapStatus)
{
    std::vector<Creature*> results;
    //returns the creature for specified faction
    for (int i=0;i < creatures.size();i++){
        //iterate and check faction
        if (creatures[i].onMap==mapStatus && creatures[i].isAlive) results.push_back(&WORLD.creatures[i]);
    }
    return results;
}

int GameWorld::getNumberOfCreatures(faction_t faction, bool mapStatus)
{
	std::vector<Creature*> results=getCreatures(faction, mapStatus);
    return results.size();
}

Item * GameWorld::generateItem(itemType_t itemType, int ownerId)
{
    Item* item=new Item(itemType, ownerId);
    items.push_back(*item);
    return item;
}

void GameWorld::addItem(Item & item)
{
    items.push_back(item);
}

Item * GameWorld::getItemforObjectId(int objectId)
{
	//returns the item pointer for objectId
	Item * item;
	for (int i=0;i < items.size();i++){
        if (items[i].objectId==objectId) item=&items[i];
    }
    return item;
}


std::vector<Item*> GameWorld::getItems(int objectId)
{
    std::vector<Item*> results;
    //returns the Items owned by the specified objectId 
    for (int i=0;i < items.size();i++){
        //iterate and check faction
//        std::cout << "item.ownerObjectId= " << items[i].ownerObjectId << std::endl;
        if (items[i].ownerObjectId==objectId) results.push_back(&WORLD.items[i]);
    }
    return results;
}

std::vector<Item*> GameWorld::getItems(itemType_t itemType)
{
    std::vector<Item*> results;
    //returns the Items for specified itemType
    for (int i=0;i < items.size();i++){
        //iterate and check faction
        if (items[i].type==itemType) results.push_back(&WORLD.items[i]);
    }
    return results;
}


std::vector<Item*> GameWorld::getItems(itemType_t itemType, bool mapStatus)
{
    std::vector<Item*> results;
    //returns the Items for specified itemType
    for (int i=0;i < items.size();i++){
        //iterate and check faction
        if (items[i].type==itemType && items[i].onMap==mapStatus) results.push_back(&WORLD.items[i]);
    }
    return results;
}

std::vector<Item*> GameWorld::getItems(bool mapStatus)
{
    std::vector<Item*> results;
    //returns the Items for the mapStatus  
    for (int i=0;i < items.size();i++){
        if (items[i].onMap==mapStatus) results.push_back(&WORLD.items[i]);
    }
    return results;
}

int GameWorld::getNumberOfItems(bool mapStatus)
{
	std::vector<Item*> results=getItems(mapStatus);
    return results.size();
}

//---DISPLAY CODE---
void GameWorld::updateMapVisibility()
{
//THIS SHOULD ALL BE MOVED INSIDE CREATURES AND ONLY TRIGGERED ON A MOVE.
	
	//mark all tiles not visible.
	for (int n=0; n< MAPHEIGHT; n++){
		for (int m=0; m< MAPWIDTH; m++){
			Position position(m,n);
			map.setTileVisibility(position, false);
			//map[n][m].isVisible = false;
		}
	}
	
	//begin new FOV code. Beware x & y coords are reversed in the MRPAS fov code.
	std::vector<Creature*> creatureList = getCreatures(FRIEND, true);
	
	for (int p = 0;p < creatureList.size();p++){
		if (creatureList[p]->isAlive){
			//tell player to fetch it's FOV
			creatureList[p]->processFOV();
			//set the players visible tiles on map
			for (int i=0;i < MAPWIDTH;i++){
				for (int j=0;j < MAPHEIGHT;j++){
					Position position(i,j);
					if (creatureList[p]->fovMap[j][i]) map.setTileVisibility(position,true);
				}
			}
		}
	}
}
	
//---END DISPLAY CODE---

std::string GameWorld::getVisibleElementNameAt(Position position)
{
	//we check entities in this order.
	//Players, Monsters, Items, Terrain and return the first positive.
	std::vector<Creature*> creatureList = getCreatures(true);

	for (int p = 0;p < creatureList.size();p++){
		if (creatureList[p]->position==position) {
			return creatureList[p]->name;
		}
	}
	//for (int p = 0;p < map.monsterList.size();p++){
//		if (map.monsterList[p].position==position) {
//			return map.monsterList[p].name;
//		}
//	}
	for (int p = 0;p < items.size();p++){
		if (items[p].position==position) {
			return items[p].name;
		}
	}
	return map.terrainNameAt(position);
}

bool GameWorld::isTileOccupied(Position position)
{
	//used to check if a creature is on a tile
	//we check entities in this order.
	//Players, Monsters, Items
	std::vector<Creature*> creatureList = getCreatures(true);

	for (int p = 0;p < creatureList.size();p++){
		if (creatureList[p]->position==position) {
			return true;
		}
	}
	//for (int p = 0;p < map.monsterList.size();p++){
//		if (map.monsterList[p].position==position) {
//			return true;
//		}
//	}
	return false;
}

bool GameWorld::isTileBlocked(Position position)
{
	if (isTileOccupied(position)) return true;
	if (map.isTerrainPassable(position)) return false;
	return true;
}

void GameWorld::damageEntityAtPosition(Position position, Item &weapon)
{
    Utility tool;
	//we check entities in this order
	//Players, Monsters, Terrain and damage in that order.
	std::vector<Creature*> creatureList = getCreatures(true);

	for (int p = 0;p < creatureList.size();p++){
		if (creatureList[p]->position==position && creatureList[p]->isAlive) {
			//found creature so lets damage it
            creatureList[p]->damage(weapon.properties["damage"]);
			
            //if creature is killed
			if (creatureList[p]->currentHitPoints<1) {
				//replace with corpse
                MSG(creatureList[p]->name + " endures an noble end");
				//Item* corpse=generateItem(CORPSE, WORLD.map.objectId);
				//corpse.name="Corpse of"+playerList[p].name; <- potential string length issue
				//corpse->position=creatureList[p]->position;
				//corpse->onMap=true;
				map.setTilePassable(creatureList[p]->position, true);
                
                //harvest his xp for the killer
				selectedPlayer->xp+=creatureList[p]->xpValue;
				
				//set creature dead. Do not delete, we may resurrect later.
                creatureList[p]->setDead();
				
				return;
			}
			MSG(creatureList[p]->name + " is hit for " + tool.stringFromInt(weapon.properties["damage"]));
			return;
		}
	}
	map.damage(position, weapon);
}

void GameWorld::initPlayers()
{
	//reset action & hit points
	std::vector<Creature*> creatureList = getCreatures(true);
	
	for (int i=0; i < creatureList.size();i++)
	{
		Creature *player = creatureList[i];
		if (player->isAlive){
			player->resetAP();
			player->resetHP();
		} 
	}
}

void GameWorld::resetPlayers()
{
	//reset action points
	std::vector<Creature*> creatureList = getCreatures(true);

	for (int i=0; i < creatureList.size();i++)
	 {
		Creature *player = creatureList[i];
		if (player->isAlive){
			player->resetAP();
		} else {
			//delete player;
			//insert corpse;
		}
	 }
}

void GameWorld::selectLivePlayer()
{
	//first check if any players still live
	std::vector<Creature*> creatureList = getCreatures(FRIEND, true);
	bool playOn=false;
	
	for (int i=0; i < creatureList.size();i++)
	{
		Creature *player = creatureList[i];
		if (player->isAlive){
			playOn=true;
		} else {
			return;
		}
	}
	if (playOn){
		if (!selectedPlayer->isAlive) selectNextPlayer();
	}
}

void GameWorld::positionPlayers()
{
	int x1=map.startx1;
	int y1=map.starty1;
	int x2=map.startx2;
	int y2=map.starty2;
	std::vector<Creature*> squad=getCreatures(FRIEND,true);
	std::vector<Creature*>::iterator creatureIt;
	for (creatureIt=squad.begin(); creatureIt!=squad.end(); ++creatureIt) {
		Creature * creat=*creatureIt;
		creat->position.x=(rand() % (x2-x1))+x1;
		creat->position.y=(rand() % (y2-y1))+y1;
	}
}

void GameWorld::selectFirstPlayer()
{	
	//select first player that's onMap and Alive
	creatureIt = creatures.begin();
	
	bool found=false;
	while (!found) {
		if ((creatureIt->onMap && creatureIt->isAlive && creatureIt->faction==FRIEND)){
			found = true;
			selectedPlayer=&*creatureIt;
			break;
		}
		++creatureIt;
	}
	std::cout << "selectedPlayer is " << selectedPlayer->name << std::endl;
}

void GameWorld::selectNextPlayer()
{
	//select next player that's onMap and Alive
	bool found=false;
	while (!found) {
		++creatureIt;
		if (creatureIt==creatures.end()) creatureIt=creatures.begin();
		if ((creatureIt->onMap && creatureIt->isAlive && creatureIt->faction==FRIEND)){
			found = true;
			selectedPlayer=&*creatureIt;
		} else {
		
		}

	}
	std::cout << "selectedPlayer is now " << selectedPlayer->name << std::endl;
}

//--selection and targetting--

void GameWorld::initSelectionCursor()
{
	target = selectedPlayer->position;
}

void GameWorld::endSelectionCursor()
{
	//selectedPlayer->isTargetting=false;
}

void GameWorld::moveSelectionCursor(int direction)
{	
	//our destination coordinates
	int destx, desty;
	Position newPosition;
	
	//set the coords of the tile we're moving to.
	switch (direction){
		case MOVE_RIGHT:
			newPosition.setXY(std::min(target.x+1,MAPWIDTH-1), target.y);
			break;
		case MOVE_LEFT:
			newPosition.setXY(std::max(target.x-1,0), target.y);
			break;
		case MOVE_UP:
			newPosition.setXY(target.x, std::max(target.y-1,0));
			break;
		case MOVE_DOWN:
			newPosition.setXY(target.x, std::min(MAPHEIGHT-1,target.y+1));
			break;
		case MOVE_UP_RIGHT:
			newPosition.setXY(std::min(target.x+1,MAPWIDTH-1), std::max(target.y-1,0));
			break;
		case MOVE_UP_LEFT:
			newPosition.setXY(std::max(target.x-1,0), std::max(target.y-1,0));
			break;
		case MOVE_DOWN_RIGHT:
			newPosition.setXY(std::min(target.x+1,MAPWIDTH-1), std::min(MAPHEIGHT-1,target.y+1));
			break;
		case MOVE_DOWN_LEFT:
			newPosition.setXY(std::max(target.x-1,0), std::min(MAPHEIGHT-1,target.y+1));
			break;
	}
	//move cursor onto new tile
	target=newPosition;
}

void GameWorld::moveSelectionCursorTo(Position pos)
{
	//move cursor onto new tile
	target=pos;
}

void GameWorld::targetNextEnemy(Creature * creature)
{
	//iterates around visible enemies
	if (!creature->visibleEnemies.empty()){
		creature->visibleEnemiesIndex++;
		if (creature->visibleEnemiesIndex > creature->visibleEnemies.size()-1) creature->visibleEnemiesIndex = 0;
		WORLD.moveSelectionCursorTo(creature->visibleEnemies[creature->visibleEnemiesIndex]);
		std::cout << "Selecting " << creature->visibleEnemiesIndex << "from " << creature->visibleEnemies.size() << std::endl;
	}
}

void GameWorld::sendMessageToConsole(std::string message)
{
	//add new message to the end of the buffer and remove the oldest from the front
	consoleBuffer.push_back(message);
	consoleBuffer.erase(consoleBuffer.begin());
}

void GameWorld::clearConsole()
{
	std::cout << "Clearing consoleBuffer" << std::endl;
	consoleBuffer.clear();
	consoleBuffer.resize(CONSOLEPANELHEIGHT);
}

//--attack actions--

void GameWorld::attack(Creature *creature)
{
	//First we work out the slope angle towards the target position...
	//Vary the angle by the modifiers and trace the projectile path until it hits
	//something or goes off the map. This may or may not hit our intended target
	// but we process the damage anyway whatever it hits.
	
	Utility tool;
	WORLD.anim.initialiseProjectile();
	
	//only attack if targetting something and we have action points
	if (!(creature->position==target) && creature->hasActionPoints(firingCost)){
		//remove AP
		creature->currentActionPoints -=firingCost;
		
		//get exact angle to the target position
		double angle = targetAngle(creature->position, target);
		
		//apply modifiers
		double modangle = modifyAimAngle(creature, angle);
		
		//get the Position we've hit.
		Position hitTile = WORLD.fireAtAngle(creature, modangle);
		
		//Draw project and oop until anim is finished.
		//matchState_t matchState=STATE_ANIMATE;
		MSG("Bang!");
		DISPLAY.drawScreen();
		DISPLAY.animate();

		//chance damage on it.
		Item * item = getItemforObjectId(creature->wielding);
		WORLD.damageEntityAtPosition(hitTile, *item);
	}
	DISPLAY.drawScreen();
}

void GameWorld::setFiringMode(firingMode_t mode)
{
	firingMode = mode;
    switch (firingMode){
        case AIMED:
            firingCost=AP_COST_AIMED;
            break;
        case SNAPSHOT:
            firingCost=AP_COST_SNAPSHOT;
            break;
        default:
            break;
    }
}

double GameWorld::targetAngle(Position src, Position dst)
{
	Utility tool;
	//short variables to make things readable;
	int x1 = src.x;
	int y1 = src.y;
	int x2 = dst.x;
	int y2 = dst.y;
	double dx = x2-x1;
	double dy = y2-y1;
	
	//convert to an angle
	double angle = atan2(dy, dx) * 180/PI;
	std::cout << "targetAngle=" << tool.stringFromDouble(angle) << std::endl;
	return angle;
}

double GameWorld::modifyAimAngle(Creature * creature, double targetAngle)
{
	Utility tool;
	
	//calc Skill modifier for selected weapon
	Item * weapon = getItemforObjectId(creature->wielding);
	std::string creatures_skill=weapon->skill;
	float skillMod=(float)creature->skills[creatures_skill]/10;
	skillMod=1/skillMod;
	
	//calc modifier for firingMode
	float firingModeMod;
	switch (firingMode) {
		case AIMED:
			firingModeMod=SKILL_AIMED_MODIFIER;
			break;
		case SNAPSHOT:
			firingModeMod=1/SKILL_SNAPSHOT_MODIFIER;
		default:
			break;
	}
	
	//calc base range constant for selected weapon
	float slope=0.5/weapon->properties["range"];
	//std::cout << "slope angle=" << tool.stringFromDouble(slope) << " range:" << creature->slot1.range << std::endl;
	float toleranceAngle = atan(slope) * 180/PI * 2;
	//std::cout << "tolerance angle=" << tool.stringFromDouble(toleranceAngle) << std::endl;
	
	//add modifiers
	toleranceAngle*=skillMod;
	std::cout << "tolerance angle after skillmod=" << tool.stringFromDouble(toleranceAngle) << " skillMod:" << tool.stringFromDouble(skillMod) << std::endl;	
	
	toleranceAngle*=firingModeMod;
	std::cout << "tolerance angle after firingModMod=" << tool.stringFromDouble(toleranceAngle) << " firingModeMod:" << tool.stringFromDouble(firingModeMod) << std::endl;	
	
	//modify our target angle by a random amount inside our tolerance.
	double angle=targetAngle+tool.randFloat(toleranceAngle*-1, toleranceAngle);
	std::cout << "final firing angle=" << tool.stringFromDouble(angle) << std::endl;
	return angle;
}

Position GameWorld::fireAtAngle(Creature * creature, double modangle)
{
	Utility tool;
	int x1 = creature->position.x;
	int y1 = creature->position.y;
	
	//convert back to slope
	double modslope=tan(modangle*PI/180);
	//	std::cout << "mod slope=" << tool.stringFromDouble(modslope) << std::endl;
	
	//work out our quadrant
	if (modangle>=-45 && modangle<=45){
		//we're on the x axis going right
		//find the slope to target
		double slope = modslope;
		int i=0;
		Position checkPosition;
		do{
			i++;
			//calc true y position;
			double j = i*slope;
			//convert to rough tile y position
			int ty = (int)(j + (j < 0 ? -0.5 : 0.5))+y1;
			if (ty>=MAPHEIGHT || ty<0) break;
			int tx = i+x1;
			if (tx>=MAPWIDTH) break;
			//std::cout << "tx=" << tx << " ty=" << tool.stringFromInt(ty) << " from " << tool.stringFromDouble(j) << std::endl;
			checkPosition.x=tx;
			checkPosition.y=ty;
			if (ty<MAPHEIGHT && ty>=0){
				WORLD.anim.coords.push_back(checkPosition);
				WORLD.anim.gfxIds.push_back(BULLET_S);
			}
			//std::cout << checkPosition.string() << " blocked = " << WORLD.isTileBlocked(checkPosition) << std::endl;
		} while (!WORLD.isTileBlocked(checkPosition));
	}
	if (modangle>=135 || modangle<=-135){
		//we're on x axise going to the left
		//find the slope to target
		double slope = modslope;
		int i=0;
		Position checkPosition;
		do{
			i--;
			//calc true y position;
			double j = i*slope;
			//convert to rough tile y position
			int ty = (int)(j + (j < 0 ? -0.5 : 0.5))+y1;
			if (ty>=MAPHEIGHT || ty<0) break;
			int tx = x1+i;
			if (tx>=MAPWIDTH || tx<0) break;
			//std::cout << "tx=" << tx << " ty=" << tool.stringFromInt(ty) << " from " << tool.stringFromDouble(j) << std::endl;
			checkPosition.x=tx;
			checkPosition.y=ty;
			if (ty<MAPHEIGHT && ty>=0){
				WORLD.anim.coords.push_back(checkPosition);
				WORLD.anim.gfxIds.push_back(BULLET_S);
			}
		} while (!WORLD.isTileBlocked(checkPosition));
	}
	if (modangle>45 && modangle<135){
		//we're on the y axis going down
		//WORLD.projectile.setVertical();
		//find the slope to target
		double slope = 1/modslope;
		int j=0;
		Position checkPosition;
		do{
			j++;
			//calc true x position;
			double i = j*slope;
			//convert to rough tile x position
			int tx = (int)(i + (i < 0 ? -0.5 : 0.5))+x1;
			if (tx>=MAPWIDTH || tx<0) break;
			int ty = j+y1;
			if (ty>=MAPHEIGHT) break;
			//std::cout << "tx=" << tool.stringFromInt(tx) << " ty=" << ty << " from " << tool.stringFromDouble(i) << std::endl;
			checkPosition.y=ty;
			checkPosition.x=tx;
			if (tx<MAPWIDTH && tx>=0){
				WORLD.anim.coords.push_back(checkPosition);
				WORLD.anim.gfxIds.push_back(BULLET_S);
			}
		} while (!WORLD.isTileBlocked(checkPosition));
	}
	if (modangle<-45 && modangle>-135){
		//we're going up
		//WORLD.projectile.setVertical();
		//find the slope to target
		double slope = 1/modslope;
		int j=0;
		Position checkPosition;
		do{
			j--;
			//calc true x position;
			double i = j*slope;
			//convert to rough tile x position
			int tx = (int)(i + (i < 0 ? -0.5 : 0.5))+x1;
			if (tx>=MAPWIDTH || tx<0) break;
			int ty = y1+j;
			if (ty<0) break;
			//std::cout << "tx=" << tool.stringFromInt(tx) << " ty=" << ty << " from " << tool.stringFromDouble(i) << std::endl;
			checkPosition.y=ty;
			checkPosition.x=tx;
			if (tx<MAPWIDTH && tx>=0){
				WORLD.anim.coords.push_back(checkPosition);
				WORLD.anim.gfxIds.push_back(BULLET_S);
			}
		} while (!WORLD.isTileBlocked(checkPosition));
		
	}
	
	//Return the last tile position
	return WORLD.anim.coords.back();
}

void GameWorld::toggleCreatureToMap(Creature *creature)
{
	//fetch list of creatures onMap already
	std::vector<Creature*> mapCreatures = WORLD.getCreatures(FRIEND, true);
	std::cout << "Creatures onMap= " << mapCreatures.size() << std::endl;
	
	if (creature->onMap){
		creature->onMap = false;
	} else {
		if (mapCreatures.size()<WORLD.map.parameterMap["squadsize"]){
			creature->onMap = true;
		}
	}
}
